#pragma once

#include "MSP.h"
#include <Windows.h>
#include <memory>
#include <conio.h>
class MSPTrainer
{
public:
	MSP* m_pMSPs;
	int m_nMSPCount;

	double* m_pLearningRates;
	double m_rEscapeMaxError;
	int m_nMaxTrainingCount;

	double* m_pXs;
	int m_nXSize;
	int* m_pTs;
	int m_nTSize;
	int m_nDataCount;

	double** m_pErrors;

	double** m_pMaxErrors;
	double* m_pBestMaxErrors;
	double** m_pBestWeights;
	int m_nBestMSPIndex;

	double* m_pMomentumRates;
	double** m_pMomentums;

	double** m_pDEDSs;

	double* m_pTrainingRates;

	bool m_bAuto;

	MSPTrainer(void){};

	MSPTrainer(int msp_count,int active_mode, int min_perceptron, int max_perceptron,  int data_count, double* x_datas, int* t_datas, int x_size, int t_size, double escape_error, int max_training_count)
		: m_nMSPCount(msp_count), m_rEscapeMaxError(escape_error), m_nMaxTrainingCount(max_training_count)
		, m_pXs(x_datas), m_nXSize(x_size), m_pTs(t_datas), m_nTSize(t_size), m_nDataCount(data_count), m_bAuto(true)
	{
		m_pMSPs=new MSP[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
		{
			int perceptron_count=randN(max_perceptron,min_perceptron);
			double active_rate=randR(2.0,0.8);
			new (m_pMSPs+i)MSP(active_rate,active_mode,x_size,t_size,perceptron_count);
		}

		m_pErrors=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pErrors[i]=new double[m_nTSize];

		m_pMaxErrors=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pMaxErrors[i]=new double [m_nDataCount];

		m_pBestMaxErrors=new double[m_nMSPCount];

		m_pBestWeights=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pBestWeights[i]=new double[m_pMSPs[i].m_nWeightCount];

		m_pMomentums=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
		{
			m_pMomentums[i]=new double[m_pMSPs[i].m_nWeightCount];
			for(int j=0;j<m_pMSPs[i].m_nWeightCount;j++)
				m_pMomentums[i][j]=0;
		}

		m_pDEDSs=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pDEDSs[i]=new double[m_pMSPs[i].m_nPerceptronCount];

		m_pMomentumRates=new double[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pMomentumRates[i]=randR(0.75,0.25);

		m_pTrainingRates=new double[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pTrainingRates[i]=randR(0.6,0.15);
		
		m_pLearningRates=new double[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pLearningRates[i]=40.0/(m_nDataCount*pow(m_nXSize*m_nTSize,0.25)*pow(m_pMSPs[i].m_nPerceptronCount-(m_nXSize>m_nTSize ? m_nXSize:m_nTSize),0.8)*m_pMSPs[i].m_rActiveRate*m_pMomentumRates[i]*m_pTrainingRates[i]);
	}

	MSPTrainer(MSP* msps, int msp_count, int data_count, double* x_datas, int* t_datas, int x_size, int t_size, double* learning_rates, double* momentum_rates
		, double escape_error, int max_training_count, double* training_rates)
		: m_pMSPs(msps), m_nMSPCount(msp_count), m_rEscapeMaxError(escape_error), m_nMaxTrainingCount(max_training_count)
		, m_pXs(x_datas), m_nXSize(x_size), m_pTs(t_datas), m_nTSize(t_size), m_nDataCount(data_count), m_bAuto(false)
	{
		m_pErrors=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pErrors[i]=new double[m_nTSize];

		m_pMaxErrors=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pMaxErrors[i]=new double [m_nDataCount];

		m_pBestMaxErrors=new double[m_nMSPCount];

		m_pBestWeights=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pBestWeights[i]=new double[m_pMSPs[i].m_nWeightCount];

		m_pMomentums=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
		{
			m_pMomentums[i]=new double[m_pMSPs[i].m_nWeightCount];
			for(int j=0;j<m_pMSPs[i].m_nWeightCount;j++)
				m_pMomentums[i][j]=0;
		}

		m_pDEDSs=new double*[m_nMSPCount];
		for(int i=0;i<m_nMSPCount;i++)
			m_pDEDSs[i]=new double[m_pMSPs[i].m_nPerceptronCount];

		m_pLearningRates=new double[m_nMSPCount];
		memcpy(m_pLearningRates,learning_rates,sizeof(double)*m_nMSPCount);

		m_pMomentumRates=new double[m_nMSPCount];
		memcpy(m_pMomentumRates,momentum_rates,sizeof(double)*m_nMSPCount);

		m_pTrainingRates=new double[m_nMSPCount];
		memcpy(m_pTrainingRates,training_rates,sizeof(double)*m_nMSPCount);
	}

	~MSPTrainer(void)
	{
		for(int i=0;i<m_nMSPCount;i++)
			delete[] m_pErrors[i];
		delete[] m_pErrors;

		for(int i=0;i<m_nMSPCount;i++)
			delete[] m_pMaxErrors[i];
		delete[] m_pMaxErrors;

		delete[] m_pBestMaxErrors;

		for(int i=0;i<m_nMSPCount;i++)
			delete[] m_pBestWeights[i];
		delete[] m_pBestWeights;

		for(int i=0;i<m_nMSPCount;i++)
			delete[] m_pMomentums[i];
		delete[] m_pMomentums;

		for(int i=0;i<m_nMSPCount;i++)
			delete[] m_pDEDSs[i];
		delete[] m_pDEDSs;

		delete[] m_pLearningRates;
		delete[] m_pMomentumRates;
		delete[] m_pTrainingRates;

		if(m_bAuto)
			delete[] m_pMSPs;
	}

	void CalcError(int msp_index, int t_index)
	{
		int* t_offset=m_pTs+m_nTSize*t_index;
		for(int i=0;i<m_nTSize;i++)
			m_pErrors[msp_index][i]=m_pMSPs[msp_index].m_pO[i]-t_offset[i];
	}

	double CalcMaxError(MSP* msp,int t_index)
	{
		int* t_offset=m_pTs+m_nTSize*t_index;
		double max_err=0;
		for(int i=0;i<m_nTSize;i++)
		{
			double err=abs(msp->m_pO[i]-t_offset[i]);
			if(max_err<err)
				max_err=err;
		}
		return max_err;
	}

	double CalcDODS(int msp_index, int perceptron_index)
	{
		MSP* msp=m_pMSPs+msp_index;
		double out=msp->m_pPerceptrons[perceptron_index].rO;
		if(msp->m_nActiveMode)
			return 0.5*msp->m_rActiveRate*(1+out)*(1-out);
		else
			return msp->m_rActiveRate*out*(1-out);
	}

	void ErrorBackPropagationWithMomentum(int msp_index)
	{
		MSP* msp=m_pMSPs+msp_index;
		for(int i=msp->m_nPerceptronCount-1; i>=0 ;i--)
		{
			double DEDO=0;
			if(i>=msp->m_nPerceptronCount-m_nTSize)
				DEDO=m_pErrors[msp_index][i-(msp->m_nPerceptronCount-m_nTSize)];
			for(int j=i+1;j<msp->m_nPerceptronCount;j++)
			{
				DEDO+=m_pDEDSs[msp_index][j]*msp->m_pPerceptrons[j].pWeights[i];
			}
			m_pDEDSs[msp_index][i]=DEDO*CalcDODS(msp_index,i);
		}

//#pragma omp parallel for
		for(int i=0; i<msp->m_nPerceptronCount;i++)
		{
			double* p_momentums=m_pMomentums[msp_index]+(msp->m_pPerceptrons[i].pWeights-msp->m_pWeights);
			for(int j=0;j<msp->m_pPerceptrons[i].nInputCount;j++)
			{
				if(j<m_nXSize)
				{
					msp->m_pPerceptrons[i].pWeights[j]+=(p_momentums[j]=
						-m_pLearningRates[msp_index]*m_pDEDSs[msp_index][i]*msp->m_pX[j] 
					+p_momentums[j]*m_pMomentumRates[msp_index]);
				}
				else
				{
					msp->m_pPerceptrons[i].pWeights[j]+=(p_momentums[j]=
						-m_pLearningRates[msp_index]*m_pDEDSs[msp_index][i]*msp->m_pPerceptrons[j-m_nXSize].rO 
						+p_momentums[j]*m_pMomentumRates[msp_index]);
				}
			}
			msp->m_pPerceptrons[i].pWeights[msp->m_nPerceptronCount]+=(p_momentums[msp->m_nPerceptronCount]=
				-m_pLearningRates[msp_index]*m_pDEDSs[msp_index][i]
			+p_momentums[msp->m_nPerceptronCount]*m_pMomentumRates[msp_index]);
		}
	}

	void Train()
	{
#ifdef USE_OMP_FOR_TRAIN
		omp_set_num_threads(m_nMSPCount);
#endif
		int key=-1;
		m_nBestMSPIndex=-1;
		int* count=new int[m_nMSPCount];
#ifdef USE_OMP_FOR_TRAIN
#pragma omp parallel for
#endif
		for(int i=0;i<m_nMSPCount;i++)
		{
			m_pBestMaxErrors[i]=-1;
			count[i]=0;
			double max_err_threshold=0;
#ifdef USE_OMP_FOR_TRAIN
#pragma omp parallel for
#endif
			for(int j=0;j<m_nDataCount;j++)
			{
				MSP msp(m_pMSPs[i]);
				msp.Stimulate(m_pXs+j*m_nXSize);
				m_pMaxErrors[i][j]=CalcMaxError(&msp,j);
			}
			max_err_threshold=QuickGet(m_pMaxErrors[i],m_nDataCount,m_nDataCount*(1-m_pTrainingRates[i]));

			do
			{
				for(int index=0;index<m_nDataCount;index++)
				{
					if(m_pMaxErrors[i][index]<max_err_threshold)
						continue;

					m_pMSPs[i].Stimulate(m_pXs+index*m_nXSize);
					CalcError(i,index);
					ErrorBackPropagationWithMomentum(i);
					count[i]++;
				}

				double max__max_err=0;
				int max__max_err__index=0;
#ifdef USE_OMP_FOR_TRAIN
#pragma omp parallel for
#endif
				for(int j=0;j<m_nDataCount;j++)
				{
					MSP msp(m_pMSPs[i]);
					msp.Stimulate(m_pXs+j*m_nXSize);
					m_pMaxErrors[i][j]=CalcMaxError(&msp,j);
				}
				for(int j=0;j<m_nDataCount;j++)
				{
					if(max__max_err<m_pMaxErrors[i][j])
					{
						max__max_err=m_pMaxErrors[i][j];
						max__max_err__index=j;
					}
				}

				if(max__max_err<m_pBestMaxErrors[i] || m_pBestMaxErrors[i]==-1)
				{
					m_pBestMaxErrors[i]=max__max_err;
					memcpy(m_pBestWeights[i],m_pMSPs[i].m_pWeights,m_pMSPs[i].m_nWeightCount*sizeof(double));
				}

				max_err_threshold=QuickGet(m_pMaxErrors[i],m_nDataCount,m_nDataCount*(1-m_pTrainingRates[i]));			
				printf("Count: %6d  MspIndex: %2d CurrentMaxErr: %.3lf BestMaxErr: %.3lf\n",count[i]/m_nDataCount,i,max__max_err,m_pBestMaxErrors[i]);
				if(kbhit())
					key=getch();
			}while(count[i]/m_nDataCount<m_nMaxTrainingCount &&  m_pBestMaxErrors[i]>m_rEscapeMaxError && !(key=='0'+i || key=='`') );

			memcpy(m_pMSPs[i].m_pWeights,m_pBestWeights[i],m_pMSPs[i].m_nWeightCount*sizeof(double));
			if(m_nBestMSPIndex==-1 || m_pBestMaxErrors[m_nBestMSPIndex]>m_pBestMaxErrors[i])
				m_nBestMSPIndex=i;
		}
		for(int i=0;i<m_nMSPCount;i++)
		{
			printf("\nMSP: %2d StopCount: %6d BestMaxErr: %.3lf\n",i,count[i]/m_nDataCount,m_pBestMaxErrors[i]);
			printf("PerceptronCount: %d ActiveRate: %.3lf\n",m_pMSPs[i].m_nPerceptronCount,m_pMSPs[i].m_rActiveRate);
			printf("LearningRate: %.8lf MomentumRate: %.3lf TrainingRate: %.3lf\n",m_pLearningRates[i],m_pMomentumRates[i],m_pTrainingRates[i]);
		}
		printf("\nBestMSP Index: %d\n",m_nBestMSPIndex);
		delete[] count;
	}
};

